DKV 答えてくれPingサーバー、ここにはuserモードとsystemdと、俺がいる!

概要

あなたが所属しているプログラミングサークルでは、Webサービスの作成を支援するため、サークルで共用のサーバーを借りて、1人1アカウントずつ割り当てて自由にサービスをデプロイできるようにしている。もちろんsudoは使えない。

あなたもGoで作ったWebアプリケーションをデプロイしたいと思ったが、定期メンテナンスで再起動されるたびに毎回アプリケーションを起動し直すのは骨が折れる。何かいい策が無いかとネットを漁ってみたが、「systemd」ってやつは管理者権限が無いと使えないらしい。管理者の先輩にアドバイスを求めたところ、「userモードのsystemdは再起動の度に起動するように設定してあるよ」とのことなので、userモードのsystemdを使ってみることにした。

実験用に作ったアプリケーションをサーバーに移しデプロイコマンドを実行して、次の定期メンテナンスを楽しみに待っていた…が。

アプリケーションが起動していない!なんで!?

前提条件

  • 解答の中で許可されるアクション
    • /home/user/webapp ディレクトリ内のファイルの変更
    • /home/user/webapp/deploy ファイルの実行
    • 上記二つに該当しない操作 (systemdをターミナルから直接操作する など) は、解決手順の中に含めてはいけません。
  • 解答として許可されない解決方法の例
    • cronで一定時間ごとに起動し続けるなど、 userモードのsystemd (systemctl --user で操作できる範囲) 以外での自動起動の実現
  • その他
    • 再起動のシミュレートのため、問題環境上では sudo コマンドを許可してありますが、回答の中では使えません
    • かわいい後輩たちのために、なぜ動いていなかったのか、原因の特定・報告をしっかりと行ってください

初期状態

  • sudo reboot でweb-serverを再起動してからweb-serverにログインして curl localhost:8080/ping を実行すると「Connection refused」というエラーが出る

終了状態

初期状態のマシン上にて提出された解答の解決手順を実行した際、以下が成立すること

  • web-serverにログインして curl localhost:8080/ping を実行すると「Hello, ICTSC2022 Contestant!」の文字列が返ってくる
  • sudo reboot でweb-serverを再起動してからweb-serverにログインして curl localhost:8080/ping を実行しても「Hello, ICTSC2022 Contestant!」の文字列が返ってくる

問題環境の再現方法

  1. CPU1コア、メモリ1GBのUbuntuが動いているVMを用意します。
  2. Go 1.20.1をインストールします。
    • https://go.dev/doc/install を参考に。
  3. userユーザーを作成します。
  4. 管理者として loginctl enable-linger user を実行します。
    • wiki にある通り、userモードのsystemdをログイン時ではなくマシンの起動時に起動するように設定するコマンドです。
  5. userユーザーとしてログインし直します。
    • su コマンドなどでユーザーを変えても、userモードのsystemdの操作はできません。
  6. https://github.com/logica0419/ictsc2022/tree/main/user-systemd にある以下の4ファイルを、/home/user/webapp ディレクトリを作ってそこに入れます。
    • deploy
    • go.mod
    • main.go
    • webapp.service
  7. chmod +x /home/user/webapp/deploy をターミナルで実行し、deployファイルを実行可能にします。
  8. mkdir -p /home/user/.config/systemd/user && ln -s /home/user/webapp/webapp.service /home/user/.config/systemd/user/webapp.service を実行してシンボリックリンクを作る
  9. /home/user/webapp/deploy をターミナルで実行すると、初期状態が再現します。

解説

トラブルの原因

解決方法

  • /home/user/webapp/deploy 内に、systemctl --user enable webapp が無いので足す
    • systemctl --user daemon-reload の後に足すのが一番良いと思うが、どこの行に書いても大丈夫
  • /home/user/webapp/webapp.service 内の、WantedBy=multi-user.targetWantedBy=default.target に変更する
  • deployファイルを実行する
    • sh /home/user/webapp/deploy bash /home/user/webapp/deploy /home/user/webapp/deploy 等の解答があったがどれも正解とした

採点基準

  • 前提条件を無視している
    • 問答無用で0点
  • enableが書けている
    • 50点
  • targetが直っている
    • 100点
  • deployファイルを実行する
    • これが無いとき、変更した設定の適応がされないので解決の意思なしとみなし0点とした

作問者コメント

  • 1日目の解答は17件来ていたが、問題文を正しく読み解いて前提条件を守った解答をしていたのが半分、その中でdeployコマンドまできちんと走らせていたものは1解答だけだった
    • このままでは本質的でない誤答があまりに多くなりすぎると思い、諦めてもう少し丁寧に条件を説明することにした (問題文中太字が追加した文面)
    • 本来問題文を注意深く読んでいればわかることだと思うので、来年度以降の参加者はもう少し丁寧に問題文を読むようにして欲しい
  • 2日目も前提条件を無視した回答は半分ほどあった
  • 元ネタはVSCodeに仕込まれていたバグ (自分がコントリビュートして解決した)

余談

チーム word-unknown-tsukuba-otaku(筑波大学) から、解答の中で以下のような指摘を受けました。作問時に全く気が付いていませんでしたがその通りです。ありがとうございます。

webapp.service の [Unit] セクションで指定されている After=network.target の記述は意味がありません。multi-user.target の時と同様に、network.target は user モードの systemd には存在しません。しかし、After= はユニットが起動する順番のみを指定し、そのユニットが起動していることを要求するものではないため、存在しないユニットをここに指定していても正常に自動起動は実現できます。